﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
using System.Windows.Forms;
using SlimDX.Direct3D9;

namespace SlimWater
{
    class Camera
    {
        protected Device device;

        protected Quaternion qRotation = Quaternion.Identity;
        protected readonly Vector3 vUnit = new Vector3(0, 0, -1);
        
        public readonly Vector3 vUpConst = new Vector3(0, 1, 0);

        protected Vector3 vEyePoint = Vector3.Zero;

        protected const float maxZ = (float)Math.PI / 3; // max Z looking-angle
        protected const float speed = 0.2f; // speed of movement

        public Camera(Device device) { this.device = device; }

        public Vector3 GetEP() { return vEyePoint; }

        public Vector3 GetLAP() // the current LAP has to be calculated
        {
            Vector3 v = Vector3.TransformCoordinate(vUnit, Matrix.RotationQuaternion(qRotation));
            return v + vEyePoint;
        }

        public void Position(Vector3 eye, Vector3 lap)
        {
            vEyePoint = eye;
            qRotation = Quaternion.RotationMatrix(Matrix.LookAtRH(eye, lap, vUpConst));
            qRotation.Invert();
        }

        public void Move(double dt)
        {
            Vector3 v = (GetLAP() - vEyePoint) * speed * (float) dt; // get the (LAP-EP) vector, scale it according to dt
            vEyePoint += v; // then add it to the current EP
        }

        public void Strafe(double dt)
        {
            Vector3 v = (GetLAP() - vEyePoint) * speed * (float) dt; // the (LAP-EP) vector, scaled according to dt
            Vector3 vOrthogonal = new Vector3(v.Z, 0.0f, -v.X); //normal vector - only on the plane x0z
            vEyePoint -= vOrthogonal; // add the normal vector to current EP
        }

        public void MoveVert(double dt) { vEyePoint.Y += speed * (float) dt; }

        public void MouseMove(int mouseDX, int mouseDY)
        {
            float tempY = (float)mouseDX / 300;
            float tempZ = (float)mouseDY / -300;

            //rotate left/right
            qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(vUpConst, tempY)); // no constraints here

            //rotate up/down
            Vector3 axis = Vector3.Cross(GetLAP() - vEyePoint, vUpConst); // rotation axis
            qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(axis, tempZ));

            //check angle constraints, block exceeding up/down rotation
            Vector3 v = Vector3.TransformCoordinate(vUnit, Matrix.RotationQuaternion(qRotation));

            double horizontal = Math.Sqrt(v.X * v.X + v.Z * v.Z);
            float upDownAngle = (float)Math.Atan(v.Y / horizontal);

            // if the calculated rotation exceeded the maximum angle - correct it by rotating back by the excess
            if (upDownAngle > maxZ)
            {
                qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(axis, maxZ - upDownAngle));
            }
            else if (upDownAngle < -maxZ)
            {
                qRotation = Quaternion.Multiply(qRotation, Quaternion.RotationAxis(axis, -maxZ - upDownAngle));
            }
        }

        public void SetView()
        {
            device.SetTransform(TransformState.View, Matrix.LookAtLH(GetEP(), GetLAP(), vUpConst));
        }


        public void SetProjection(float nearest, float furthest)
        {
            device.SetTransform(TransformState.Projection, Matrix.PerspectiveFovLH((float)Math.PI / 4, Screen.PrimaryScreen.Bounds.Width / Screen.PrimaryScreen.Bounds.Height, nearest, furthest));
        }
    }
}
